﻿using System.Collections.Generic;
using System.Text;
using FlexminAPI;
using System.Net.Sockets;
using System.IO;
using System.Windows.Forms;
using System;
using System.Net;
using System.Net.NetworkInformation;
using System.Threading;
using FlexLib;

namespace FlexminAdmin
{
	internal class RemoteComputer : IHost
	{
		private const int CHECK_INTERVAL = 10000;

		private Thread statusCheckThread = null;
		private ManualResetEvent manualReset = null;

		/// <summary>
		/// Конструктор
		/// </summary>
		/// <param name="addr">Адрес хоста</param>
		/// <param name="password">Пароль</param>
		public RemoteComputer(string addr, string password)
		{
			_password = password;
			ChangeAddress(addr);
		}

		/// <summary>
		/// Конструктор с паролем по умолчанию
		/// </summary>
		/// <param name="addr">Адрес хоста</param>
		public RemoteComputer(string addr)
		{
			_password = "";
			ChangeAddress(addr);
		}

		public List<PluginInfo> plugins;

		public ICollection<PluginInfo> GetPlugins()
		{
			return plugins;
		}

		/// <summary>
		/// Состояние удаленного компьютера
		/// </summary>
		private ComputerState state;
		public ComputerState State
		{
			get { return state; }
		}

		public event HostUpdateDelegate StateChanged;

		/// <summary>
		/// Адрес удаленного компьютера
		/// </summary>
		private string hostAddress = null;
		/// <summary>
		/// Адрес удаленного компьютера
		/// </summary>
		public string HostAddress
		{
			get { return hostAddress; } 
			set { ChangeAddress(value); }
		}

        /// <summary>
        /// Пароль
        /// </summary>
        private string _password = null;
        /// <summary>
		/// Пароль
        /// </summary>
        public string Password
        {
			get { return _password; }
            set { ChangePassword(value); }
        }

		/// <summary>
		/// Смена пароля
		/// </summary>
		/// <param name="newPassword">Новый пароль</param>
		private void ChangePassword(string newPassword)
		{
			Monitor.Enter(this);

			// Если старый пароль не верен, то меняем только на стороне админки
			if (state == ComputerState.NotAutentificated)
			{
				_password = newPassword;
				Monitor.Exit(this);
				Refresh();
				return;
			}

			// Если старый пароль верен - значит нужно сменить парол на стороне хоста
			string oldPass = _password;
			_password = newPassword;

			IPHostEntry hostEntry = null;

			bool error = false;
			try
			{
				hostEntry = Dns.GetHostEntry(hostAddress);
			}
			catch
			{
				error = true;
			}

			if (error || (hostEntry.AddressList.Length < 1))
			{
				Monitor.Exit(this);
				Refresh();
				return;
			}

			IPAddress address = hostEntry.AddressList[0];

			SocketWriter writer = null;
			SocketReader reader = null;
			try
			{
				IPEndPoint ipe = new IPEndPoint(address, NetHelp.SERVER_TCP_PORT);
				Socket tempSocket = new Socket(ipe.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

				tempSocket.Connect(ipe);

				if (tempSocket.Connected)
				{
					writer = new SocketWriter(tempSocket);
					reader = new SocketReader(tempSocket);
				}

				if (writer == null)
				{
					Monitor.Exit(this);
					Refresh();
					return;
				}
			}
			catch (SocketException)
			{
				Monitor.Exit(this);
				Refresh();
				return;
			}

			try
			{
				writer.WriteString(oldPass);
				writer.WriteInt(NetHelp.ACTION_CHANGE_PASSWORD);
				writer.WriteString(_password);

				int answer = reader.ReadInt();
			}
			catch
			{
				writer.Close();
				Monitor.Exit(this);
				Refresh();
				return;
			}

			Monitor.Exit(this);
			Refresh();
		}

		/// <summary>
		/// Посылка Ping запроса удаленному компьютеру
		/// </summary>
		/// <param name="address">Адрес удаленного компьютера</param>
		/// <returns>Получен ли ответ</returns>
		private bool PingComputer(IPAddress address)
		{
			Ping ping = new Ping();
			PingReply reply;

			try
			{
				reply = ping.Send(address, 3000);
			}
			catch
			{
				return false;
			}

			if (reply.Status != IPStatus.Success)
			{
				try
				{
					reply = ping.Send(address, 3000);
				}
				catch
				{
					return false;
				}

				if (reply.Status != IPStatus.Success)
				{
					try
					{
						reply = ping.Send(address, 3000);
					}
					catch
					{
						return false;
					}

					if (reply.Status != IPStatus.Success)
					{
						return false;
					}
				}
			}

			return true;
		}

		/// <summary>
		/// Соединение с сервисом
		/// </summary>
		/// <param name="address">Адрес компьютера с сервисом</param>
		/// <returns>Состояние удаленного компьютера</returns>
		private ComputerState ConnectToService(IPAddress address)
		{
			SocketWriter writer = null;
			SocketReader reader = null;
			try
			{
				IPEndPoint ipe = new IPEndPoint(address, NetHelp.SERVER_TCP_PORT);
				Socket tempSocket = new Socket(ipe.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

				tempSocket.Connect(ipe);

				if (tempSocket.Connected)
				{
					writer = new SocketWriter(tempSocket);
					reader = new SocketReader(tempSocket);
				}

				if (writer == null)
				{
					return ComputerState.On;
				}
			}
			catch (SocketException)
			{
				return ComputerState.On;
			}

			string pluginsList;
			try
			{
				writer.WriteString(_password);
				int answer = reader.ReadInt();
				if (answer != NetHelp.PASSWORD_OK)
				{
					writer.Close();
					return ComputerState.NotAutentificated;
				}

				writer.WriteInt(NetHelp.ACTION_LIST_PLUGINS);
				pluginsList = reader.ReadString();
			}
			catch
			{
				writer.Close();
				return ComputerState.On;
			}

			writer.Close();

			string[] descStrings = pluginsList.Split(new char[] { '\t' }, StringSplitOptions.RemoveEmptyEntries);
			int plugCount = int.Parse(descStrings[0]);


			List<PluginInfo> newPlugins = new List<PluginInfo>();

			for (int iPlugin = 0; iPlugin < plugCount; ++iPlugin)
			{
				string[] versionParts = descStrings[iPlugin * 3 + 3].Split(new char[] { '.' });
				Version ver = new Version(int.Parse(versionParts[0]),
											int.Parse(versionParts[1]),
											int.Parse(versionParts[2]),
											int.Parse(versionParts[3]));

				PluginInfo desc = new PluginInfo(descStrings[iPlugin * 3 + 1],
													descStrings[iPlugin * 3 + 2],
													"",
													ver,
													false,
													false);
				newPlugins.Add(desc);
			}

			bool pluginsEquals = true;
			if (plugins.Count != newPlugins.Count)
				pluginsEquals = false;
			else
			{
				for (int i = 0; i < plugins.Count; i++)
				{
					if (!plugins[i].Equals(newPlugins[i]))
					{
						pluginsEquals = false;
						break;
					}
				}
			}

			Monitor.Enter(this);
			if (state != ComputerState.Ready || !pluginsEquals)
			{
				plugins = newPlugins;
				state = ComputerState.Ready;
				Monitor.Exit(this);

				if (StateChanged != null)
					StateChanged(this, new HostEventArgs());
			}
			else
				Monitor.Exit(this);

			return ComputerState.Ready;
		}

		/// <summary>
		/// Функция потока проверки статуса компьютера
		/// </summary>
		private void StatusCheckThreadEntry()
		{
			IPHostEntry hostEntry = null;
			IPAddress address = null;

			try
			{
				hostEntry = Dns.GetHostEntry(hostAddress);

				foreach (IPAddress addr in hostEntry.AddressList)
				{
					if (addr.AddressFamily == AddressFamily.InterNetwork)
					{
						address = addr;
						break;
					}
				}
			}
			catch { }

			if (address == null)
			{
				IPAddress.TryParse(hostAddress, out address);
			}

			if (address == null)
			{
				Monitor.Enter(this);
				plugins = new List<PluginInfo>();
				state = ComputerState.Off;
				Monitor.Exit(this);

				if (StateChanged != null)
					StateChanged(this, new HostEventArgs());

				return;
			}

			while (true)
			{
				//Проверка компа пингом
				if (!PingComputer(address))
				{
					Monitor.Enter(this);
					if (state != ComputerState.Off)
					{
						plugins = new List<PluginInfo>();
						state = ComputerState.Off;
						Monitor.Exit(this);

						if (StateChanged != null)
							StateChanged(this, new HostEventArgs());
					}
					else
						Monitor.Exit(this);

					if (manualReset.WaitOne(CHECK_INTERVAL, false))
					{
						return;
					}
					continue;
				}

				if (state == ComputerState.Off)
				{
					Monitor.Enter(this);
					plugins = new List<PluginInfo>();
					state = ComputerState.On;
					Monitor.Exit(this);

					if (StateChanged != null)
						StateChanged(this, new HostEventArgs());
				}

				//Если пингуется, то теперь проверяем наличие службы попыткой коннекта
                ComputerState newState = ConnectToService(address);
				if (newState != ComputerState.Ready)
				{
					Monitor.Enter(this);
                    if (state != newState)
					{
						plugins = new List<PluginInfo>();
                        state = newState;
						Monitor.Exit(this);

						if (StateChanged != null)
							StateChanged(this, new HostEventArgs());
					}
					else
						Monitor.Exit(this);

					if (manualReset.WaitOne(CHECK_INTERVAL, false))
					{
						return;
					}
					continue;
				}

				if (manualReset.WaitOne(CHECK_INTERVAL, false))
				{
					return;
				}		
			}
		}

		/// <summary>
		/// Смена адреса удаленного компа
		/// </summary>
		/// <param name="addr">Адрес</param>
		private void ChangeAddress(string addr)
		{
			hostAddress = addr;

			Refresh();
		}

		/// <summary>
		/// Немедленное обновление состояния удаленного компьютера
		/// </summary>
		public void Refresh()
		{
			StopThread();

			plugins = new List<PluginInfo>();
			state = ComputerState.Off;

			manualReset = new ManualResetEvent(false);

			statusCheckThread = new Thread(StatusCheckThreadEntry);
			statusCheckThread.Start();
		}

		/// <summary>
		/// Остановка потока проверки статуса
		/// </summary>
		private void StopThread()
		{
			if (statusCheckThread != null)
			{
				manualReset.Set();
				statusCheckThread.Abort();
			}
		}

		/// <summary>
		/// Завершение работы с удаленным компьютером
		/// </summary>
		public void ShutDown()
		{
			StateChanged = null;
			StopThread();
		}

		public override string ToString()
		{
			return hostAddress;
		}
	}
}
